
using System;
using System.Collections.Generic;

namespace Ack
{		
	public class NesDecoder2
	{
		private static byte[] DecodeRCR(ByteSupplier previous)
		{
			// There's a one byte header, get it.
			byte rcrHeader = previous.ReadByte();

			// First row is always stored, rows 1-7 can be repeated from previous row.
			byte[] pattern = new byte[8];
			pattern[0] = previous.ReadByte();
			if((rcrHeader & 0x40)!=0) pattern[1] = previous.ReadByte(); else pattern[1] = pattern[0];
			if((rcrHeader & 0x20)!=0) pattern[2] = previous.ReadByte(); else pattern[2] = pattern[1];
			if((rcrHeader & 0x10)!=0) pattern[3] = previous.ReadByte(); else pattern[3] = pattern[2];
			if((rcrHeader & 0x08)!=0) pattern[4] = previous.ReadByte(); else pattern[4] = pattern[3];
			if((rcrHeader & 0x04)!=0) pattern[5] = previous.ReadByte(); else pattern[5] = pattern[4];
			if((rcrHeader & 0x02)!=0) pattern[6] = previous.ReadByte(); else pattern[6] = pattern[5];
			if((rcrHeader & 0x01)!=0) pattern[7] = previous.ReadByte(); else pattern[7] = pattern[6];
			
			// If bit 7 is set in the header, the repeats were column-oriented.
			if((rcrHeader & 0x80)!=0)
				pattern = Misc.Transpose8x8(pattern,0);		
			
			return pattern;
		}
		
		public static List<byte> Decode(ByteSupplier previous)
		{
			List<byte> output = new List<byte>();
			
			byte paletteMapping = previous.ReadByte();
			
			byte[][] commonPatterns = new byte[][] { new byte[] { 0,0,0,0,0,0,0,0 },new byte[] { 255,255,255,255,255,255,255,255 } };
			int[] commonIndices = { previous.ReadByte() + 256*previous.ReadByte(), 
									previous.ReadByte() + 256*previous.ReadByte() };
			
			int gpi = 0;
			while(previous.HasMoreBytes())
			{
				byte groupHeader = previous.ReadByte();
				byte groupHeader2 = previous.ReadByte();
				byte groupHeader3 = previous.ReadByte();
				
				// Now decode eight patterns.
				for(int pi = 0; pi < 8; ++pi,++gpi)
				{
					int patternMode = (((groupHeader >> (7-pi)) & 1) << 2) | (((groupHeader2 >> (7-pi)) & 1) << 1) | ((groupHeader3 >> (7-pi)) & 1);
					
					switch(patternMode)
					{
						case 0: // Raw
						{
							if(!previous.HasMoreBytes())
								break;
							for(int ri = 0; ri < 8; ++ri)
							{
								output.Add(previous.ReadByte());
							}
						
							break;
						}
						case 1: // RCR
						{
							if(!previous.HasMoreBytes())
								break;
							output.AddRange(DecodeRCR(previous));
							break;
						}
						case 2: // Common pattern A
						{
							output.AddRange(commonPatterns[0]);
							break;
						}
						case 3: // Common pattern B
						{
							output.AddRange(commonPatterns[1]);
							break;
						}
						case 4: // Same as last pattern
						{
							output.AddRange(output.GetRange(output.Count-8,8));
							break;
						}
						case 5: // Same as last but inverted
						{
							List<byte> pattern = output.GetRange(output.Count-8,8);
							for(int i = 0; i < 8; ++i)
							{
								pattern[i] = (byte)~pattern[i];
							}
							output.AddRange(pattern);
							break;
						}
					    case 6: // RD
						case 7: // RD (y-flipped)
						{
							// There's a two byte header, get it.
							byte rdHeader1 = previous.ReadByte();
							byte rdHeader2 = previous.ReadByte();
						
						    int offset = ((rdHeader1 & 0x7F)+1) * 8; // offset in patterns starting from the last -> byte offset from end of output
						
							List<byte> referencePattern = output.GetRange(output.Count-offset,8);
						
							byte[] pattern = new byte[8];

							bool flipx = (rdHeader1 & 0x80)!=0;
							bool flipy = patternMode == 7;
						
							int bitmask = 0x80;
							for(int i = 0; i < 8; ++i, bitmask>>=1)
							{
								if((rdHeader2 & bitmask)!=0) 
								{
									pattern[i] = previous.ReadByte(); 
								}
								else
								{
									pattern[i] = (byte)(referencePattern[flipy?7-i:i]);
									if(flipx) pattern[i] = Misc.ReverseByte(pattern[i]);
								}
							}
						
							output.AddRange(pattern);
							break;
						}
					}
					
					for(int i = 0; i < commonIndices.Length; ++i)
						if(commonIndices[i] == gpi)
						{
							commonPatterns[i] = output.GetRange(output.Count-8, 8).ToArray();
						}
				} 
			}
			
			int[][] mappings = new int[][] { 
				new int[]{ 0, 1, 2, 3 },
				new int[]{ 0, 2, 1, 3 },
				new int[]{ 0, 1, 3, 2 },
				new int[]{ 0, 2, 3, 1 },
				new int[]{ 0, 3, 1, 2 },
				new int[]{ 0, 3, 2, 1 } };

			int[][] invmappings = new int[6][];
			for(int i = 0; i < 6; ++i)
			{
				invmappings[i] = new int[4];
				for(int j = 0; j < 4; ++j)
				{
					invmappings[i][mappings[i][j]] = j;
				}
			}

			NesEncoder2 enc = new NesEncoder2();
			return new List<byte>(enc.RemapColors(output.ToArray(), invmappings[paletteMapping]));
		}

	}
}
